package p2pscrabble;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Random;

import javax.swing.JOptionPane;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class Game implements Runnable {

	private Connection connection;
	public String player1;
	public String player2;
	private boolean iAmPlayer1;
	private boolean myTurn = false;

	private MainWindow scrab;
	private String saveName;

	public Dictionary dict;
	private TilesBag bag;
	private GameState now;
	private GameState prev;
	private GameMove gameMove;

	private boolean can_undo = false;
	private int swap_count = 0;
	private XStream xs = new XStream(new DomDriver());

	public Game(Connection connection, String player1, String player2,
			boolean isHost, GameState gs) {
		this.connection = connection;
		this.player1 = player1;
		this.player2 = player2;
		this.iAmPlayer1 = isHost;
		dict = new Dictionary();

		// game state initially

		Calendar cal = Calendar.getInstance();
		SimpleDateFormat sdf = new SimpleDateFormat("MMdd");
		saveName = player1.substring(0, 4) + player2.substring(0, 4)
				+ sdf.format(cal.getTime()) + (new Random().nextInt(100));

		now = new GameState();
		prev = null;

		gameMove = new GameMove(saveName + ".txt");

		init_turn();
		init_bag();
		init_window();

		if (gs == null) {
			if ((myTurn && isHost) || (!myTurn && !isHost)) {
				now.setPlayer1Turn(true);
				now.setPlayer1Letter(bag.getAllLetters(0, 7));
				now.setPlayer2Letter(bag.getAllLetters(7, 14));
			} else {
				now.setPlayer1Turn(false);
				now.setPlayer1Letter(bag.getAllLetters(7, 14));
				now.setPlayer2Letter(bag.getAllLetters(0, 7));
			}

			now.setBagLetter(bag.getAllLetters());
			now.setBagCounter(bag.getCounter());
			changeMyTurn(myTurn);
		} else {
			now = gs;
			revertToNowState();
		}

		now.print();
		new InfoWindow(1, "Save Game Name", "Your save game name is "
				+ saveName);
		run();
	}

	private void init_turn() {
		Random r = new Random();
		int a = r.nextInt(2);
		connection.send_data(a + "");
		int b = Integer.parseInt(connection.receive_data());
		a = (a + b) % 2;
		myTurn = (iAmPlayer1 && a == 0) || (!iAmPlayer1 && !(a == 0)) ? true
				: false;
	}

	private void init_bag() {
		char[] letters;
		if (myTurn) {
			bag = new TilesBag();
			bag.shuffleLetters();
			letters = bag.getAllLetters();
			connection.send_data(String.valueOf(letters));
		}

		else {
			connection.wait_for_data();
			letters = connection.receive_data().toCharArray();
			bag = new TilesBag(letters, 6);
		}
	}

	private void init_window() {
		scrab = new MainWindow(this);
		scrab.setSize(800, 600);
		scrab.setVisible(true);
		scrab.setEnabled(true);
		scrab.setResizable(false);
	}

	public TilesBag getBag() {
		return bag;
	}

	public boolean isMyTurn() {
		return myTurn;
	}

	public boolean amIPlayer1() {
		return iAmPlayer1;
	}

	public GameState getNowState() {
		return now;
	}

	public void submit(String words_and_score, char[][] boardLetter, int score,
			char[] curLetters) {
		connection.send_data("SUBMIT=" + words_and_score);
		changeMyTurn(false);
		can_undo = true;

		// save the previous state
		prev = new GameState(now);
		now.setBoardLetter(boardLetter);

		// state string format = player1Letter; player2Letter; bagLetter;
		// bagCounter; player1Score; player2Score; player1Turn

		String letterState = iAmPlayer1 ? new String(curLetters) + "; ;" : " ;"
				+ new String(curLetters) + ";";
		String scoreState = iAmPlayer1 ? score + ";0;" : "0;" + score + ";";
		Boolean turnState = iAmPlayer1 ? myTurn : !myTurn;

		String state = letterState + " ; ;" + scoreState + turnState;
		connection.send_data("STATE=" + state);
		updateState(state);

		gameMove.addMove(iAmPlayer1 ? 1 : 2, "submit", words_and_score);

		if (bag.getCounter() >= 99
				&& (new String(curLetters)).compareTo("-------") == 0) {
			// end game
			connection.send_data("ENDGAME");
			processCommand(999, "");
		}
	}

	public void swap(char[] curLetters) {
		if (curLetters == null)
			connection.send_data("SWAP=" + String.valueOf(bag.getAllLetters()) + "1");
		else
			connection.send_data("SWAP=" + String.valueOf(bag.getAllLetters()));
		
		changeMyTurn(false);
		swap_count = curLetters != null ? swap_count + 1 : swap_count;

		String letterState = curLetters == null ? " ; ;"
				: iAmPlayer1 ? new String(curLetters) + "; ;" : " ;"
						+ new String(curLetters) + ";";
		Boolean turnState = iAmPlayer1 ? myTurn : !myTurn;
		// state string format = player1Letter; player2Letter; bagLetter;
		// bagCounter; player1Score; player2Score; myTurn
		String state = letterState + new String(bag.getAllLetters())
				+ "; ;0;0;" + turnState;
		connection.send_data("STATE=" + state);
		updateState(state);

		if (curLetters != null)
			gameMove.addMove(iAmPlayer1 ? 1 : 2, "swap", new String(bag.getAllLetters()));
	}

	public void pass() {
		connection.send_data("PASS");
		changeMyTurn(false);
		swap_count++;
		now.setPlayer1Turn(!now.getPlayer1Turn());
		now.setTurn(now.getTurn() + 1);
		sendMyTime();

		gameMove.addMove(iAmPlayer1 ? 1 : 2, "pass", "");
	}

	public void undo_request() {
		if (!can_undo) {
			new InfoWindow(1, "Undo not permitted",
					"You can not request for undo now!");
			return;
		}
		connection.send_data("UNDOREQUEST");
		can_undo = false;
	}

	public String checkWords(String s) {
		String wrongWords = "";
		if (s.contains(";")) {
			String[] split = s.split(";");
			for (int i = 2; i < split.length; i++) {
				if (!dict.checkWordExist(split[i]))
					wrongWords += "\n- " + split[i].toUpperCase();
			}
		} else {
			if (!dict.checkWordExist(s))
				wrongWords = s;
		}
		return wrongWords;
	}

	public void sendMyTime() {
		String data = "TIME-ME=" + scrab.getMyTime();
		connection.send_data(data);
	}

	public void sendChatMessage(String s) {
		connection.send_data("CHAT=" + s);
	}

	@Override
	public void run() {
		String data;
		while (true) {
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
			}

			connection.wait_for_data();
			data = connection.receive_data();
			// System.out.println(data);

			if (data.startsWith("SUBMIT="))
				processCommand(1, data);
			else if (data.startsWith("SWAP="))
				processCommand(2, data);
			else if (data.startsWith("UNDO="))
				processCommand(3, data);
			else if (data.startsWith("STATE="))
				processCommand(4, data);
			else if (data.startsWith("PASS"))
				processCommand(5, data);
			else if (data.startsWith("UNDOREQUEST"))
				processCommand(10, data);
			else if (data.startsWith("TIME-ME="))
				processCommand(50, data);
			else if (data.startsWith("CHAT="))
				processCommand(70, data);
			else if (data.startsWith("SURRENDER"))
				processCommand(100, data);
			else if (data.startsWith("ENDGAME"))
				processCommand(999, data);

			if (!connection.socket.isConnected()
					|| !connection.socket.isBound())
				processCommand(999, data);
		}
	}

	private void processCommand(int type, String data) {

		switch (type) {
		case 1:
			// push the previous state
			prev = new GameState(now);

			data = data.substring(7);
			String[] split = data.split(",");

			char[] letters = new char[split.length];
			int[] row = new int[split.length];
			int[] col = new int[split.length];

			for (int i = 0; i < split.length; i++) {
				String[] split2 = split[i].split(":");
				letters[i] = split2[0].charAt(0);
				int rowcol = Integer.parseInt(split2[1]);
				row[i] = rowcol / 100;
				col[i] = rowcol % 100;
				now.setBoardLetter(row[i], col[i], letters[i]);
			}

			bag.setCounter(bag.getCounter() + letters.length);

			scrab.submitOppTiles(letters, row, col);
			scrab.submit_action(false);
			changeMyTurn(true);

			gameMove.addMove(iAmPlayer1 ? 2 : 1, "submit", data);

			break;
		case 2:
			boolean undo_or_swap = false;
			data = data.substring(5);
			if (data.charAt(data.length() - 1) == '1') {
				data = data.substring(0, data.length() - 1);
				undo_or_swap = true;
			}
			
			bag.setLetter(data.toCharArray());
			// System.out.println(bag.getAllLetters());
			scrab.swap_action(false);
			changeMyTurn(true);

			if (undo_or_swap)
				gameMove.addMove(iAmPlayer1 ? 1 : 2, "undo", data);
			else
				gameMove.addMove(iAmPlayer1 ? 2 : 1, "swap", data);
			break;

		case 3:
			data = data.substring(5);
			if (data.equals("OK"))
				undo_action(false);
			break;

		case 4:
			updateState(data);
			break;

		case 5:
			changeMyTurn(true);
			swap_count++;
			now.setPlayer1Turn(!now.getPlayer1Turn());
			now.setTurn(now.getTurn() + 1);
			gameMove.addMove(iAmPlayer1 ? 2 : 1, "pass", "");

			break;

		case 10:
			undo_popup();
			break;

		case 50:
			data = data.substring(8);
			scrab.setTimer(Long.parseLong(data));
			break;

		case 70:
			data = data.substring(5);
			scrab.appendChatMessage(data);
			break;

		case 100:
			end(1, 0, 0);
			break;

		case 999:
			int player1Score = now.getPlayer1Score();
			int player2Score = now.getPlayer2Score();

			if (myTurn && iAmPlayer1) {
				player1Score -= 2 * calculatePenalty(now.getPlayer1Letter());
			} else {
				player2Score -= 2 * calculatePenalty(now.getPlayer2Letter());
			}

			end(0, player1Score, player2Score);
			break;

		default:
			break;
		}
	}

	public void undo_popup() {
		int undoConfirm;
		undoConfirm = JOptionPane.showConfirmDialog(null,
				"Your opponent request undo the last move.\n"
						+ "Do you want to allow?", "Confirm Undo",
				JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);

		if (undoConfirm == 0) {
			connection.send_data("UNDO=OK");
			undo_action(true);
		} else {
			connection.send_data("UNDO=NO");
		}
	}

	public void undo_action(boolean shuffle) {
		now = new GameState(prev);
		now.setTurn(now.getTurn() + 1);
		prev = null;
		bag.setCounter(now.getBagCounter());

		// after undo, letters are re-shuffled and re-distributed by 1 player;
		if (shuffle) {
			bag.shuffleLetters();
			now.setBagLetter(bag.getAllLetters());
			swap(null);
			
			gameMove.addMove(
					(shuffle && iAmPlayer1) || (!shuffle && !iAmPlayer1) ? 2 : 1,
					"undo", new String(bag.getAllLetters()));
		}

		// revert to previous state;
		revertToNowState();
	}

	public void revertToNowState() {
		bag.setCounter(now.getBagCounter());
		bag.setLetter(now.getBagLetter());
		scrab.setScore(now.getPlayer1Score(), now.getPlayer2Score());

		if (iAmPlayer1) {
			scrab.setTiles(now.getBoardLetter(), now.getPlayer1Letter());
			changeMyTurn(now.getPlayer1Turn());
		} else {
			scrab.setTiles(now.getBoardLetter(), now.getPlayer2Letter());
			changeMyTurn(!now.getPlayer1Turn());
		}
	}

	public void end(int type, int myScore, int oppScore) {
		switch (type) {
		case 1: // opponent surrender
			new InfoWindow(100, "Your opponent surrender !!",
					"Thank you for playing");
			break;
		case 2: // i surrender
			connection.send_data("SURRENDER!!");
			new InfoWindow(100, "You Surrender !!", "Thank you for playing");
			break;
		default: // normal game (all of the tiles are played or
			// swap 3 times in a row)
			String endTitle;
			if (myScore > oppScore)
				endTitle = "You Win !!";
			else if (myScore == oppScore)
				endTitle = "Draw !!";
			else
				endTitle = "You Lose !!";

			new InfoWindow(100, endTitle, "Your score =\t" + myScore
					+ "\nOpponent Score =\t" + oppScore
					+ "\nThank you for playing");
			break;
		}
	}

	public void saveGame() {
		now.writeIntoFile(saveName, iAmPlayer1);
	}

	private void changeMyTurn(boolean b) {
		myTurn = b;
		scrab.updateMainWindowView(b);
		if (myTurn)
			can_undo = !b;
	}

	private void updateState(String s) {
		if (s.startsWith("STATE=")) {
			s = s.substring(6);
		}

		// state string format = myLetter; oppLetter; bagLetter; bagCounter;
		// myScore; oppScore; myTurn
		now.updateState(s, bag.getCounter());

		saveGame();
		now.print();
	}

	private int calculatePenalty(char[] letterLeft) {
		int penalty = 0;
		for (int i = 0; i < 7; i++) {
			if (letterLeft[i] != ' ' && letterLeft[i] != '-') {
				penalty += bag.getLetterScore(letterLeft[i]);
			}
		}
		return penalty;
	}

	public GameState fromString(String s) {
		return (GameState) xs.fromXML(s);
	}
}
